﻿//+-------------------------------------------------------------------------------------------------------------------------------------------------------
//
//Description: DeploySettingsPageViewModel
//Author: Aravind Ramarathinam (aravr@microsoft.com)
//
//Copyright © Microsoft Corporation
//Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
//You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 
//
//THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, 
//INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. 
//See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
// 
//--------------------------------------------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using VmroleAuthor.Models;

namespace VmroleAuthor.ViewModels
{
    public class DeploySettingsPageViewModel : DeployPageViewModelBase
    {
        private readonly ObservableCollection<ResExtDeploymentParamSettingViewModel> resExtDeploymentParamSettingViewModels;
        private bool deploymentStarted;

        private string waitMessage = string.Empty;
        // lock for wait message
        private static readonly ReaderWriterLock RwWmLock = new ReaderWriterLock();

        private const int RwTimeoutinMilliSeconds = 30000;

        private const string AzureWaitMessage = " Please wait while the configuration is processed . ";

        public string WaitMessage
        {
            get
            {
                try
                {
                    RwWmLock.AcquireReaderLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        return this.waitMessage;
                    }
                    finally
                    {
                        RwWmLock.ReleaseReaderLock();
                    }
                }
                catch (ApplicationException)
                {
                    // log this
                }
                return string.Empty;
            }
        }

        public object WaitMessageVisibility
        {
            get
            {
                return (string.IsNullOrEmpty(this.WaitMessage) ? Visibility.Hidden : Visibility.Visible);
            }
        }

        private void UpdateWaitMessage(string updateMessage = null)
        {
            try
            {
                RwWmLock.AcquireWriterLock(RwTimeoutinMilliSeconds);
                try
                {
                    if (updateMessage == null)
                    {
                        // a . update mg is called
                        if (string.IsNullOrEmpty(this.waitMessage))
                        {
                            this.waitMessage = AzureWaitMessage;
                        }
                        else
                        {
                            this.waitMessage = this.waitMessage + " . ";
                        }
                    }
                    else if (updateMessage == string.Empty)
                    {
                        // a clear wait mg is called
                        this.waitMessage = updateMessage;
                    }
                    else
                    {
                        // a new wait mg is called
                        if (string.IsNullOrEmpty(this.waitMessage))
                        {
                            this.waitMessage = updateMessage;
                        }
                        else
                        {
                            this.waitMessage = this.waitMessage + " . ";
                        }
                    }
                }
                finally
                {
                    RwWmLock.ReleaseWriterLock();
                }
            }
            catch (ApplicationException)
            {
                // log this
            }

            this.OnPropertyChanged("WaitMessage");
            this.OnPropertyChanged("WaitMessageVisibility");
        }

        public DeploySettingsPageViewModel(VmRoleResourceExtensionViewModel resExtViewModel,
            ResExtAzureDeploymentData deployData)
            : base(resExtViewModel, deployData)
        {
            this.resExtDeploymentParamSettingViewModels = new ObservableCollection<ResExtDeploymentParamSettingViewModel>();
            this.PageTitle = "DEPLOYMENT CONFIGURATION";
            foreach (ParameterViewModel parameterViewModel in this.ResExtViewModel.ResourceExtensionParametersViewModel.Parameters)
            {
                this.resExtDeploymentParamSettingViewModels.Add(new ResExtDeploymentParamSettingViewModel(parameterViewModel));  
            }
        }

        public static class PropertyNames
        {
            public const string ResExtDeploymentParamSettingViewModels = "ResExtDeploymentParamSettingViewModels";
            public const string IsUserInputComplete = "IsUserInputComplete";
            public const string SettingsTitle = "SettingsTitle";      
        }


        public ObservableCollection<ResExtDeploymentParamSettingViewModel> ResExtDeploymentParamSettingViewModels
        {
            get { return this.resExtDeploymentParamSettingViewModels; }
        }


        public string SettingsTitle
        {
            get
            {
                return this.resExtDeploymentParamSettingViewModels.Any() ? "APPLICATION SETTINGS" : "NO APPLICATION SETTINGS TO CONFIGURE";
            }
        }


        internal override bool IsUserInputComplete
        {
            get
            {
                return this.resExtDeploymentParamSettingViewModels.All(resExtDeploymentParamSettingViewModel => resExtDeploymentParamSettingViewModel.IsParameterSettingAvailable);              
            }
        }

        internal override bool PerformNextStep()
        {
            //If not done already, Queue a new thread. Pass it the settings & deployment data.
            if (!deploymentStarted && this.IsUserInputComplete)
            {
                // create the settings body
                this.SetSettings(true);
                if (string.IsNullOrEmpty(this.DeployData.AzureResExtBase64EncodedProtectedSettings))
                {
                    this.UpdateWaitMessage(string.Empty);
                    return false;
                }
                this.UpdateWaitMessage(string.Empty);
                ThreadPool.QueueUserWorkItem(this.DeploymentThreadPoolCallback, this.DeployData);
                deploymentStarted = true;
                return true;
            }
            this.UpdateWaitMessage(string.Empty);
            return false;
        }

        private void SetSettings(bool uploadRequired)
        {
            this.DeployData.ApplicationParameterSettingValues = this.GetApplicationParameterSettingValues();
            try
            {
                UpdateWaitMessage(AzureWaitMessage);

                string unsubstitutedJson = this.ResExtViewModel.GetPrettyJson(true);
                this.DeployData.ResourceExtensionJson = ParameterizationHelper.SubstituteParamConfigurationValuesInJson(unsubstitutedJson, this.DeployData.ApplicationParameterSettingValues);                
                this.UpdateWaitMessage();

                VMRoleResourceExtensionDefinition resextObj = GetObjectFromJsonString<VMRoleResourceExtensionDefinition>(this.DeployData.ResourceExtensionJson);

                // push the resources to the storage container
                if (AzureHelper.UploadResExtResources(this.ResExtViewModel, this.DeployData, uploadRequired))
                {
                    this.UpdateWaitMessage();
                    // upload succeeded
                    resextObj = AzureHelper.AdjustApplicationPayloadPaths(resextObj, this.DeployData);
                }
                else
                {
                    return;
                }

                AzureExtensionHandlerPrivateSettings privateSettings = new AzureExtensionHandlerPrivateSettings();
                privateSettings.VMMEHV1 = new AzureExtensionHandlerV1Settings("Converge", "Rest", resextObj);
                using (MemoryStream ms = new MemoryStream())
                {
                    StreamReader reader = new StreamReader(ms);
                    DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(AzureExtensionHandlerPrivateSettings));
                    ser.WriteObject(ms, privateSettings);
                    ms.Position = 0;
                    this.DeployData.AzureResExtProtectedSettings = reader.ReadToEnd();
                }
                this.UpdateWaitMessage();

                this.DeployData.AzureResExtBase64EncodedProtectedSettings = Convert.ToBase64String(Encoding.UTF8.GetBytes(this.DeployData.AzureResExtProtectedSettings));
            }
            catch (Exception ex)
            {
                string error = string.Format(
                  "Error while generating resource extension settings for {0}. Exception details : {1}",
                  this.ResExtViewModel.Name ?? "Unspecified name",
                  ex.Message);
                MessageBox.Show(error, DefinitionViewModel.ToolHeader, MessageBoxButton.OK, MessageBoxImage.Exclamation);   
            }
            this.UpdateWaitMessage(string.Empty);
        }

        internal override string GetSettings()
        {
            if (string.IsNullOrEmpty(this.DeployData.AzureResExtProtectedSettings))
            {
                this.SetSettings(false);
            }
            return this.DeployData.AzureResExtProtectedSettings;
        }

        internal override string GetPowerShellScript()
        {
            if (string.IsNullOrEmpty(this.DeployData.AzureResExtProtectedSettings))
            {
                this.SetSettings(false);
            }
            return AzureHelper.GetPowerShellScript(this.DeployData);
        }

        private static TT GetObjectFromJsonString<TT>(string jsonStr)
        {
            using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonStr)))
            {
                DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(TT));
                TT deserializedObject = (TT)ser.ReadObject(ms);
                return deserializedObject;
            }          
        }

        private Dictionary<string, string> GetApplicationParameterSettingValues()
        {
            Dictionary<string, string> settingValues = this.resExtDeploymentParamSettingViewModels.ToDictionary(resExtDeploymentParamSettingViewModel => resExtDeploymentParamSettingViewModel.Name, resExtDeploymentParamSettingViewModel => resExtDeploymentParamSettingViewModel.SettingValue);            
            return settingValues;
        }


        private void DeploymentThreadPoolCallback(Object threadContext)
        {
            ResExtAzureDeploymentData deploymentData = threadContext as ResExtAzureDeploymentData;
            Debug.Assert(deploymentData != null);

            UpdateWaitMessage(deploymentData, ResExtAzureDeploymentStatus.InProgress,
                HandlerStatusStates.Transitioning, "Deployment to Microsoft Azure is in progress ");

            // flag tracks the commitNull operation
            bool commitNullSucceeded = true;
            string convergeConfig = this.DeployData.AzureResExtBase64EncodedProtectedSettings ;
           
            // at this stage, the waga on the vm is either not installed or in a ready state
            // check the EH status and clear its goalstate if it is failed.
            if (deploymentData.SelectedVirtualMachine.ResourceExtensionStatus != null &&
                deploymentData.SelectedVirtualMachine.ResourceExtensionStatus.ExtensionSettingStatus != null)
            {
                if (deploymentData.SelectedVirtualMachine.ResourceExtensionStatus.ExtensionSettingStatus.Status == HandlerStatusStates.Success)
                {
                    deploymentData.LastUpdatedTimestamp = deploymentData.SelectedVirtualMachine.ResourceExtensionStatus.ExtensionSettingStatus.ConfigurationAppliedTime;
                }
                else if (deploymentData.SelectedVirtualMachine.ResourceExtensionStatus.ExtensionSettingStatus.Status == HandlerStatusStates.Warning)
                {
                    deploymentData.LastUpdatedTimestamp = deploymentData.SelectedVirtualMachine.ResourceExtensionStatus.ExtensionSettingStatus.Timestamp;
                }
                else if (deploymentData.SelectedVirtualMachine.ResourceExtensionStatus.ExtensionSettingStatus.Status == HandlerStatusStates.Error)
                {
                    deploymentData.LastUpdatedTimestamp = deploymentData.SelectedVirtualMachine.ResourceExtensionStatus.ExtensionSettingStatus.Timestamp;
                    //
                    // the EH on the VM is in failed state, clear the VM goalstate
                    // try and a perform a commit null
                    // create a blank ResExt with a windows app profile
                    ResourceExtensionStorageInfo resourceExtensionStorageInfo = new ResourceExtensionStorageInfo
                    {
                        FileToSavePath = null,
                        JsonFileName = "Blank",
                        WorkingDir = null
                    };
                    VmRoleResourceExtensionViewModel resExtVM =
                        VmRoleResourceExtensionViewModel.CreateNewResourceExtension(resourceExtensionStorageInfo);
                    // set the windows app profile value to null
                    resExtVM.ResourceExtensionDefinition.ExtensionSettings.ApplicationProfile.WindowsApplicationProfile
                        = null;

                    // create a new protected settings for the new ResExt
                    AzureExtensionHandlerPrivateSettings privateSettings = new AzureExtensionHandlerPrivateSettings();
                    privateSettings.VMMEHV1 = new AzureExtensionHandlerV1Settings("SeedGoalState", "Rest",
                        resExtVM.ResourceExtensionDefinition);
                    string commitNullJson;
                    using (MemoryStream ms = new MemoryStream())
                    {
                        StreamReader reader = new StreamReader(ms);
                        DataContractJsonSerializer ser =
                            new DataContractJsonSerializer(typeof (AzureExtensionHandlerPrivateSettings));
                        ser.WriteObject(ms, privateSettings);
                        ms.Position = 0;
                        commitNullJson = reader.ReadToEnd();
                    }

                    deploymentData.AzureResExtBase64EncodedProtectedSettings =
                        Convert.ToBase64String(Encoding.UTF8.GetBytes(commitNullJson));

                    // Make the REST Calls to Azure on failure
                    string failureresponseId = null;
                    bool failureResult = AzureHelper.UpdateVMRoleRestRequest(deploymentData, ref failureresponseId);

                    UpdateWaitMessage(deploymentData, ResExtAzureDeploymentStatus.InProgress,
                           HandlerStatusStates.Transitioning, "Deployment to Microsoft Azure is in progress ");

                    if (failureResult)
                    {
                        long cnCount = 0;
                        // check the status of the revert request
                        do
                        {
                            cnCount++;
                            Thread.Sleep(5000);
                            AzureHelper.GetUpdateVmRoleStatus(deploymentData, failureresponseId, cnCount);
                            deploymentData.OnDeploymentUpdated();
                        } while (deploymentData.ResExtAzureDeploymentStatus == ResExtAzureDeploymentStatus.InProgress);
                    }

                    if (deploymentData.SummaryResultMessage.Category == HandlerStatusStates.Error)
                    {
                        // indicate that we failed to deploy the clear the VM  
                        string errorMessage =
                            "Deployment of Resource Extension failed. Please check the Virtual Machine.";

                        UpdateWaitMessage(deploymentData, ResExtAzureDeploymentStatus.Failed,
                            HandlerStatusStates.Error, errorMessage);

                        // reset the flag 
                        commitNullSucceeded = false;
                    }
                    else
                    {
                        UpdateWaitMessage(deploymentData, ResExtAzureDeploymentStatus.InProgress,
                            HandlerStatusStates.Transitioning, "Deployment to Microsoft Azure is in progress ");
                        long cnCount = 0;
                        // check to see whether commit null succeeded
                        do
                        {
                            cnCount++;
                            Thread.Sleep(15000);
                            AzureHelper.UpdateVmRoleEhStatus(deploymentData, cnCount);
                            deploymentData.OnDeploymentUpdated();
                        } while ((deploymentData.ResExtAzureDeploymentStatus == ResExtAzureDeploymentStatus.InProgress));

                        if (deploymentData.ResExtAzureDeploymentStatus == ResExtAzureDeploymentStatus.Failed)
                        {
                            // reset the flag 
                            commitNullSucceeded = false;
                        }
                        else
                        {
                            // update the last updated DateTime  value to indicate the commit null was the last sucesssful update
                            deploymentData.LastUpdatedTimestamp = deploymentData.SelectedVirtualMachine.ResourceExtensionStatus.ExtensionSettingStatus.ConfigurationAppliedTime;
                            // commit null succeeded, proceed
                            UpdateWaitMessage(deploymentData, ResExtAzureDeploymentStatus.InProgress,
                                HandlerStatusStates.Transitioning, "Deployment to Microsoft Azure is in progress ");
                        }
                    }
                }
            }

            if (commitNullSucceeded)
            {
                // Make the REST Calls to Azure to invoke update
                // restore the converge message
                deploymentData.AzureResExtBase64EncodedProtectedSettings = convergeConfig;

                string responseId = null;
                bool result = AzureHelper.UpdateVMRoleRestRequest(deploymentData, ref responseId);

                if (result)
                {
                    int count = 0;
                    do
                    {
                        count++;
                        Thread.Sleep(5000);
                        AzureHelper.GetUpdateVmRoleStatus(deploymentData, responseId, count);
                        deploymentData.OnDeploymentUpdated();
                    } while ((deploymentData.ResExtAzureDeploymentStatus == ResExtAzureDeploymentStatus.InProgress));
                }
                else
                {
                    // update wait message to failed
                    UpdateWaitMessage(deploymentData, ResExtAzureDeploymentStatus.Failed,
                        HandlerStatusStates.Error, "Deployment of Resource Extension failed");
                }

                // if the VM Role status is reporting success, we can track the goal state converge operation status
                if (deploymentData.ResExtAzureDeploymentStatus == ResExtAzureDeploymentStatus.CompletedWithSuccess)
                {
                    // update the wait message 
                    UpdateWaitMessage(deploymentData, ResExtAzureDeploymentStatus.InProgress,
                        HandlerStatusStates.Transitioning, "Getting the configuration status ");

                    int count = 0;
                    do
                    {
                        count++;
                        Thread.Sleep(15000);
                        AzureHelper.UpdateVmRoleEhStatus(deploymentData, count);
                        deploymentData.OnDeploymentUpdated();
                    } while ((deploymentData.ResExtAzureDeploymentStatus == ResExtAzureDeploymentStatus.InProgress));
                }
                deploymentData.OnDeploymentUpdated();
            }

            // check if the delete after deploy flag is on
            if (deploymentData.HasPayload && deploymentData.DeleteResourcesAfterDeployment)
            {
                // remove any uploaded resources
                AzureHelper.RemoveUploadedResExtesources(deploymentData);
            }

            // trigger the ui thread
            this.CurrentDispatcher.Invoke((ThreadStart)CommandManager.InvalidateRequerySuggested);
        }

        private void UpdateWaitMessage(ResExtAzureDeploymentData deploymentData,
            ResExtAzureDeploymentStatus status, HandlerStatusStates category,
            string message)
        {
            deploymentData.ResExtAzureDeploymentStatus = status;
            deploymentData.SummaryResultMessage = new ResExtDeploymentResult
            {
                Category = category,
                Message = message
            };
            deploymentData.OnDeploymentUpdated();
        }
    }
}
